/***********************************************
 This file is part of redia-lib-utils-ios
 
 
 Copyright (c) 2015 Redia A/S
 
 Redia-library-app-ios is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 Redia-library-app-ios is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with redia-library-app-ios.  If not, see <http://www.gnu.org/licenses/>.
 
 *********************************************** */

#import "JSONEncodingHelperMethods.h"
#import "JSONEncoding.h"

#ifdef DEBUG_JSONLIB
#import "defines.h"
#endif

@implementation JSONEncodingHelperMethods

+ (id)parseObjectOfClass:(Class<JSONEncoding>)objectClass withKey:(NSString *)key fromJsonObject:(NSDictionary *)JsonObj
{
    // Fetch the value and do type checking.
    id value = [JsonObj objectForKey:key];
    if (value == nil) {
#ifdef DEBUG_JSONLIB
        DLog(@"Key with name \"%@\" not found in JSON document.", key);
#endif
        return nil;
    }
    return [JSONEncodingHelperMethods parseObjectOfClass:objectClass fromJsonObject:value];
}

+ (id)parseObjectOfClass:(Class<JSONEncoding>)objectClass fromJsonObject:(id)JsonObj
{
    id value = JsonObj;
    
    if (value == [NSNull null]) {
        // NULL is a valid object value.
        return nil;
    }
    if (![value isKindOfClass:[NSDictionary class]]) {
#ifdef DEBUG_JSONLIB
        DLog(@"Unexpected type found in JSON document.");
#endif
        return nil;
    }

    // This removes any keys that start with __
    // TODO: Maybe this should be removed when the server side has removed the superfluous keys?
    NSMutableArray *removeThese = [NSMutableArray array];
    for (id entryKey in [(NSDictionary*)value allKeys]) {
        if ([entryKey isKindOfClass:[NSString class]] && [entryKey length] > 2 && [[entryKey substringToIndex:2] compare:@"__"] == NSOrderedSame) {
            [removeThese addObject:entryKey];
        }
    }
    if ([removeThese count] > 0) {
        NSMutableDictionary *newValue = [NSMutableDictionary dictionaryWithDictionary:value];
        for (NSString *removeThis in removeThese) {
            [newValue removeObjectForKey:removeThis];
        }
        value = [NSDictionary dictionaryWithDictionary:newValue];
    }
    
    id result = nil;
    if ([value count] != 0) {
        result = [objectClass objectWithJSONObject:value];
    }
    return result;
}

+ (NSInteger)parseIntegerWithKey:(NSString *)key fromJsonObject:(NSDictionary *)JsonObj
{
    return [self parseIntegerWithKey:key fromJsonObject:JsonObj error:nil];
}

+ (NSInteger)parseIntegerWithKey:(NSString *)key fromJsonObject:(NSDictionary *)JsonObj error:(NSError**)error
{
    return [self parseIntegerWithKey:key fromJsonObject:JsonObj acceptNull:NO error:error];
}

+ (NSInteger)parseIntegerWithKey:(NSString *)key
                  fromJsonObject:(NSDictionary *)JsonObj
                      acceptNull:(BOOL)acceptNull
                           error:(NSError *__autoreleasing *)error
{
    // Fetch the value and do type checking.
    id value = [JsonObj objectForKey:key];
    if (value == nil) {
        if (error) {
            *error = [NSError errorWithDomain:@"JSONEncoding" code:JSONEncodingErrorKeyNotFound userInfo:nil];
        }
        return 0;
    }
    if (![value isKindOfClass:[NSNumber class]]) {
        if (acceptNull && [value isKindOfClass:[NSNull class]]) {
            return 0;
        }
#ifdef DEBUG_JSONLIB
        DLog(@"Unexpected type found in JSON document. Key = %@", key);
#endif
        if (error) {
            *error = [NSError errorWithDomain:@"JSONEncoding" code:JSONEncodingErrorTypeMismatch userInfo:nil];
        }
        return 0;
    }
    
    return [(NSNumber*)value integerValue];
}

+ (float)parseFloatWithKey:(NSString *)key fromJsonObject:(NSDictionary *)JsonObj
{
    return [self parseFloatWithKey:key fromJsonObject:JsonObj error:nil];
}

+ (float)parseFloatWithKey:(NSString *)key fromJsonObject:(NSDictionary *)JsonObj error:(NSError**)error
{
    // Fetch the value and do type checking.
    id value = [JsonObj objectForKey:key];
    if (value == nil) {
        if (error) {
            *error = [NSError errorWithDomain:@"JSONEncoding" code:JSONEncodingErrorKeyNotFound userInfo:nil];
        }
        return 0.0;
    }
    if (![value isKindOfClass:[NSNumber class]]) {
#ifdef DEBUG_JSONLIB
        DLog(@"Unexpected type found in JSON document. Key = %@", key);
#endif
        if (error) {
            *error = [NSError errorWithDomain:@"JSONEncoding" code:JSONEncodingErrorTypeMismatch userInfo:nil];
        }
        return 0.0;
    }
    
    return [(NSNumber*)value floatValue];
}

+ (double)parseDoubleWithKey:(NSString *)key fromJsonObject:(NSDictionary *)JsonObj
{
    return [self parseDoubleWithKey:key fromJsonObject:JsonObj error:nil];
}

+ (double)parseDoubleWithKey:(NSString *)key fromJsonObject:(NSDictionary *)JsonObj error:(NSError**)error
{
    // Fetch the value and do type checking.
    id value = [JsonObj objectForKey:key];
    if (value == nil) {
        if (error) {
            *error = [NSError errorWithDomain:@"JSONEncoding" code:JSONEncodingErrorKeyNotFound userInfo:nil];
        }
        return 0.0;
    }
    if (![value isKindOfClass:[NSNumber class]]) {
#ifdef DEBUG_JSONLIB
        DLog(@"Unexpected type found in JSON document. Key = %@", key);
#endif
        if (error) {
            *error = [NSError errorWithDomain:@"JSONEncoding" code:JSONEncodingErrorTypeMismatch userInfo:nil];
        }
        return 0.0;
    }
    
    return [(NSNumber*)value doubleValue];
}

+ (BOOL)parseBoolWithKey:(NSString *)key fromJsonObject:(NSDictionary *)JsonObj
{
    return [self parseBoolWithKey:key fromJsonObject:JsonObj error:nil];
}

+ (BOOL)parseBoolWithKey:(NSString *)key fromJsonObject:(NSDictionary *)JsonObj error:(NSError**)error
{
    // Fetch the value and do type checking.
    id value = [JsonObj objectForKey:key];
    if (value == nil) {
        if (error) {
            *error = [NSError errorWithDomain:@"JSONEncoding" code:JSONEncodingErrorKeyNotFound userInfo:nil];
        }
        return 0;
    }
    if (![value isKindOfClass:[NSNumber class]]) {
#ifdef DEBUG_JSONLIB
        DLog(@"Unexpected type found in JSON document. Key = %@", key);
#endif
        if (error) {
            *error = [NSError errorWithDomain:@"JSONEncoding" code:JSONEncodingErrorTypeMismatch userInfo:nil];
        }
        return 0;
    }
    
    return [(NSNumber*)value boolValue];
}

+ (NSArray *)parseArrayOfClass:(Class<JSONEncoding>)objectClass withKey:(NSString *)key fromJsonObject:(NSDictionary *)JsonObj
{
    // Fetch the value and do type checking.
    id value = [JsonObj objectForKey:key];
    if (value == nil) {
        return nil;
    }
    if (![value isKindOfClass:[NSArray class]]) {
#ifdef DEBUG_JSONLIB
        DLog(@"Unexpected type found in JSON document. Key = %@, value was class %@, should have been %@", key, [value class], objectClass);
#endif
        return nil;
    }
   
    NSMutableArray *array = [[NSMutableArray alloc] init];
    for (id arrayElement in value) {
        if ([arrayElement isKindOfClass:[NSDictionary class]]) {
            id object = [objectClass objectWithJSONObject:arrayElement];
            if (object) {
                [array addObject:object];
            }
            else {
#ifdef DEBUG_JSONLIB
                DLog(@"Error parsing object with key: %@", key);
#endif
                return nil;
            }
        }
        else {
#ifdef DEBUG_JSONLIB
            DLog(@"Unexpected type of object found in JSON array. Key = %@", key);
#endif
            return nil;
        }
    }
    
    return [NSArray arrayWithArray:array];
}

+ (NSArray*)parseArrayOfClass:(Class<JSONEncoding>)objectClass fromArray:(NSArray*)array;
{
    NSMutableArray *result = [[NSMutableArray alloc] init];
    for (id arrayElement in array) {
        if ([arrayElement isKindOfClass:[NSDictionary class]]) {
            id object = [objectClass objectWithJSONObject:arrayElement];
            if (object) {
                [result addObject:object];
            }
            else {
                return nil;
            }
        }
        else {
            return nil;
        }
    }
    
    return [NSArray arrayWithArray:result];
}

+ (NSDictionary *)parseDictionaryWithKey:(NSString *)key fromJsonObject:(NSDictionary *)JsonObj
{
    id value = [JsonObj objectForKey:key];
    if (value == nil) {
        return nil;
    }
    
    if (![value isKindOfClass:[NSDictionary class]]) {
#ifdef DEBUG_JSONLIB
        DLog(@"Unexpected type found in JSON document. Key = %@", key);
#endif
        return nil;
    }
    
    return value;
}

+(NSDictionary *)parseDictionaryOfStringsToClass:(Class<JSONEncoding>)objectClass withKey:(NSString*)key fromJsonObject:(NSDictionary *)JsonObj
{
    id value = [JsonObj objectForKey:key];
    if (value == nil) {
        return nil;
    }

    return [self parseDictionaryOfStringsToClass:objectClass fromJsonObject:value];
}


+(NSDictionary *)parseDictionaryOfStringsToClass:(Class<JSONEncoding>)objectClass fromJsonObject:(NSDictionary *)JsonObj
{
    // Fetch the value and do type checking.
    if (![JsonObj isKindOfClass:[NSDictionary class]]) {
#ifdef DEBUG_JSONLIB
        DLog(@"Unexpected type found in JSON document. Value was class %@, should have been NSDictionary", [JsonObj class]);
#endif
        return nil;
    }
    
    NSMutableDictionary *new_dictionary = [[NSMutableDictionary alloc] init];
    for (NSString* key in [JsonObj allKeys]) {
        if ([key isKindOfClass:[NSString class]]) {
            id dictionary_value = [JsonObj objectForKey:key];
            if (dictionary_value==[NSNull null]) {
                return nil;
            } else {
                id object = [objectClass objectWithJSONObject:dictionary_value];
                if (object) {
                    [new_dictionary setValue:object forKey:key];
                }
                else {
#ifdef DEBUG_JSONLIB
                    DLog(@"Error parsing object with key: %@", key);
#endif
                    return nil;
                }
            }
        }
        else {
#ifdef DEBUG_JSONLIB
            DLog(@"Unexpected type of key found in JSON dictionary. Key = %@", key);
#endif
            return nil;
        }
    }
    
    return [NSDictionary dictionaryWithDictionary:new_dictionary];
}

+(NSDictionary *)parseDictionaryOfClass:(Class<JSONEncoding>)keyClass
                                toClass:(Class<JSONEncoding>)objectClass
                                withKey:(NSString*)key
                         fromJsonObject:(NSDictionary *)JsonObj
{
    id value = [JsonObj objectForKey:key];
    if (value == nil) {
        return nil;
    }
    
    return [self parseDictionaryOfClass:keyClass toClass:objectClass fromJsonObject:value];
}


+(NSDictionary *)parseDictionaryOfClass:(Class<JSONEncoding>)keyClass
                                toClass:(Class<JSONEncoding>)objectClass
                         fromJsonObject:(NSDictionary *)JsonObj
{
    // Fetch the value and do type checking.
    if (![JsonObj isKindOfClass:[NSDictionary class]]) {
#ifdef DEBUG_JSONLIB
        DLog(@"Unexpected type found in JSON document. Value was class %@, should have been NSDictionary", [JsonObj class]);
#endif
        return nil;
    }
    
    NSMutableDictionary *new_dictionary = [[NSMutableDictionary alloc] init];
    for (id key in [JsonObj allKeys]) {
        if ([key isKindOfClass:keyClass]) {
            id dictionary_value = [JsonObj objectForKey:key];
            if (dictionary_value==[NSNull null]) {
                return nil;
            } else {
                id object = [objectClass objectWithJSONObject:dictionary_value];
                if (object) {
                    [new_dictionary setValue:object forKey:key];
                }
                else {
#ifdef DEBUG_JSONLIB
                    DLog(@"Error parsing object.");
#endif
                    return nil;
                }
            }
        }
        else {
#ifdef DEBUG_JSONLIB
            DLog(@"Unexpected type of key found in JSON dictionary.");
#endif
            return nil;
        }
    }
    
    return [NSDictionary dictionaryWithDictionary:new_dictionary];
}

+ (NSArray *)parseArrayOfStringsWithKey:(NSString *)key fromJsonObject:(NSDictionary *)JsonObj
{
    // Fetch the value and do type checking.
    id value = [JsonObj objectForKey:key];
    if (value == nil) {
        return nil;
    }
    if (![value isKindOfClass:[NSArray class]]) {
#ifdef DEBUG_JSONLIB
        DLog(@"Unexpected type found in JSON document. Key = %@", key);
#endif
        return nil;
    }
    
    // Do simple type checking.
    for (id element in value) {
        if (![element isKindOfClass:[NSString class]]) {
            return nil;
        }
    }
    
    return value;
}

+ (NSString*)parseStringWithKey:(NSString*)key fromJsonObject:(NSDictionary*)JsonObj
{
    id value = [JsonObj objectForKey:key];
    if (value == nil) {
        return nil;
    }
    if (![value isKindOfClass:[NSString class]]) {
#ifdef DEBUG_JSONLIB
        DLog(@"Unexpected type found in JSON document. Key = %@", key);
#endif
        return nil;
    }
    
    return value;
}

+ (NSDate*)parseDateWithKey:(NSString*)key dateFormat:(NSString*)dateFormat fromJsonObject:(NSDictionary*)jsonObj
{
    id value = [jsonObj objectForKey:key];
    if (value == nil) {
        return nil;
    }
    if (![value isKindOfClass:[NSString class]]) {
#ifdef DEBUG_JSONLIB
        DLog(@"Unexpected type found in JSON document. Key = %@", key);
#endif
        return nil;
    }
    
    NSDateFormatter *formatter = [NSDateFormatter new];
    formatter.dateFormat = dateFormat;
    return [formatter dateFromString:value];
}

+ (NSArray*)jsonifyArrayOfObjects:(NSArray*)array
{
    NSMutableArray *result = [NSMutableArray array];
    for (id<JSONEncoding> object in array) {
        if (![object conformsToProtocol:@protocol(JSONEncoding)]) {
#ifdef DEBUG_JSONLIB
            DLog(@"Attempt to jsonify non-JSON object.");
#endif
            return nil;
        }
        [result addObject:[object toJSONObject]];
    }
    return [NSArray arrayWithArray:result];
}

+ (NSDictionary*)jsonifyDictionaryOfStringToObject:(NSDictionary*)dictionary
{
    NSMutableDictionary *result = [NSMutableDictionary new];
    for (NSString *key in dictionary.allKeys) {
        if ([[dictionary objectForKey:key] conformsToProtocol:@protocol(JSONEncoding)] == NO) {
#ifdef DEBUG_JSONLIB
            DLog(@"Attempt to jsonify non-JSON object.");
#endif
            return nil;
        }
        [result setObject:[[dictionary objectForKey:key] toJSONObject] forKey:key];
        
    }
    return [NSDictionary dictionaryWithDictionary:result];
}

+ (NSDictionary*)jsonifyDictionaryOfObjectToObject:(NSDictionary*)dictionary
{
    NSMutableDictionary *result = [NSMutableDictionary new];
    for (id key in dictionary.allKeys) {
        if ([key conformsToProtocol:@protocol(JSONEncoding)] == NO) {
#ifdef DEBUG_JSONLIB
            DLog(@"Attempt to jsonify non-JSON key.");
#endif
            return nil;
        }
        if ([[dictionary objectForKey:key] conformsToProtocol:@protocol(JSONEncoding)] == NO) {
#ifdef DEBUG_JSONLIB
            DLog(@"Attempt to jsonify non-JSON value.");
#endif
            return nil;
        }
        [result setObject:[[dictionary objectForKey:key] toJSONObject] forKey:[key toJSONObject]];
    }
    return [NSDictionary dictionaryWithDictionary:result];
}

@end
